package main;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JPanel;

@SuppressWarnings("serial")
class ApproximationPanel extends JPanel {
	private final int STEPS = 25;
	private ControlCurve curve = new ControlCurve();

	Cubic[] calcAcyclicNormalCubic(int n, int[] x, int[] y) {
		Cubic[] C = new Cubic[n];
		float[] t = new float[n];
		float[] R = new float[n - 1];
		float[][] M = new float[n - 1][n - 1];
		float[] D = new float[n];
		for (int i = 0; i < n - 1; i++) {
			for (int j = 0; j < n - 1; j++) {
				M[i][j] = 0;
			}
		}
		for (int i = 0; i < n; i++) {
			t[i] = 1;
		}
		M[0][0] = 2 * (1 + t[n - 1] / t[1]);
		M[0][1] = t[n - 1] / t[1];
		M[0][n - 2] = -1;

		for (int i = 1; i < n - 1; i++) {
			M[i][i] = 2 * (t[i] + t[i - 1]);
		}
		for (int i = 1; i < n - 1; i++) {
			M[i][i - 1] = t[i];
		}
		for (int i = 1; i < n - 2; i++) {
			M[i][i + 1] = t[i - 1];
		}
		R[0] = 3 * t[n - 1] / (t[1] * t[1]) * (x[1] - x[0]) + 3 / t[n - 1]
				* (x[n - 1] - x[0]);
		for (int i = 1; i < n - 1; i++) {
			R[i] = 3
					/ (t[i] * t[i + 1])
					* (t[i] * t[i] * (x[i + 1] - x[i]) + t[i + 1] * t[i + 1]
							* (x[i] - x[i - 1]));
		}
		for (int i = 0; i < n - 1; i++) {
			for (int j = 0; j < n - 1; j++) {
				// System.out.print(M[i][j] + "\t");
			}
			// System.out.println();
		}
		// System.out.println();
		float[] Der = new float[n - 1];
		Der = Matrix.multi(Matrix.inv(M), R);
		for (int i = 0; i < n - 1; i++) {
			D[i] = Der[i];
		}
		D[n - 1] = -D[0];

		for (int i = 0; i < n - 1; i++) {
			C[i] = new Cubic(x[i], D[i], 3 * (x[i + 1] - x[i])
					/ (t[i + 1] * t[i + 1]) - 2 * D[i] / t[i + 1] - D[i + 1]
					/ t[i + 1], 2 * (x[i] - x[i + 1])
					/ (t[i + 1] * t[i + 1] * t[i + 1]) + D[i]
					/ (t[i + 1] * t[i + 1]) + D[i + 1] / (t[i + 1] * t[i + 1]));
		}
		C[n - 1] = new Cubic(x[n - 1], D[n - 1], 3 * (x[0] - x[n - 1])
				/ (t[0] * t[0]) - 2 * D[n - 1] / (t[0]) - D[0] / (t[0]), 2
				* (x[n - 1] - x[0]) / (t[0] * t[0] * t[0]) + D[n - 1]
				/ (t[0] * t[0]) + D[0] / (t[0] * t[0]));
		return C;
	}

	public void drawSpline(Graphics g) {
		curve.paint(g);
		if (curve.pts.npoints > 3) {
			g.setColor(new Color(255, 0, 0));
			Cubic[] X = calcAcyclicNormalCubic(curve.pts.npoints,
					curve.pts.xpoints, curve.pts.ypoints);
			Cubic[] Y = calcAcyclicNormalCubic(curve.pts.npoints,
					curve.pts.ypoints, curve.pts.xpoints);
			Polygon p = new Polygon();
			p.addPoint((int) Math.round(X[0].eval(0)),
					(int) Math.round(Y[0].eval(0)));
			for (int i = 0; i < X.length; i++) {
				for (int j = 1; j <= STEPS; j++) {
					float u = j / (float) STEPS;
					p.addPoint((int) Math.round(X[i].eval(u)),
							(int) Math.round(Y[i].eval(u)));
				}
			}
			g.drawPolyline(p.xpoints, p.ypoints, p.npoints);
		}
	}

	public void paint(Graphics g) {
		RenderingHints rH = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		rH.put(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);
		((Graphics2D) g).setRenderingHints(rH);
		paintComponent((Graphics2D) g);
	}

	public void paintComponent(Graphics2D g) {
		super.paintComponent(g);
		drawSpline(g);
	}

	class ControlMouseMotionListener implements MouseMotionListener {

		@Override
		public void mouseDragged(MouseEvent e) {
			curve.setPoint(e.getX(), e.getY());
			repaint();
		}

		@Override
		public void mouseMoved(MouseEvent e) {

		}

	}

	class ControlMouseListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent e) {
			curve.addPoint(e.getX(), e.getY());
			repaint();
		}

		@Override
		public void mouseEntered(MouseEvent e) {

		}

		@Override
		public void mouseExited(MouseEvent e) {

		}

		@Override
		public void mousePressed(MouseEvent e) {

		}

		@Override
		public void mouseReleased(MouseEvent e) {

		}
	}

	public ApproximationPanel() {
		setBackground(Color.WHITE);
		addMouseMotionListener(new ControlMouseMotionListener());
		addMouseListener(new ControlMouseListener());
	}
}